/*
*  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
*  reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
*  2. Redistributions in binary form must reproduce the above copyright
*  notice, this list of conditions and the following discalimer in
*  the documentation and/or other materials provided with the
*  distribution.
*
*  3. The end-user documentation included with the redistribution,
*  if any, must include the following acknowledgment:
*  "This product includes software developed by the
*  Sun Microsystems, Inc. for Project JXTA."
*  Alternately, this acknowledgment may appear in the software itself,
*  if and wherever such third-party acknowledgments normally appear.
*
*  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
*  must not be used to endorse or promote products derived from this
*  software without prior written permission. For written
*  permission, please contact Project JXTA at http://www.jxta.org.
*
*  5. Products derived from this software may not be called "JXTA",
*  nor may "JXTA" appear in their name, without prior written
*  permission of Sun.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
*  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
*  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
*  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*  SUCH DAMAGE.
*  ====================================================================
*
*  This software consists of voluntary contributions made by many
*  individuals on behalf of Project JXTA.  For more
*  information on Project JXTA, please see
*  <http://www.jxta.org/>.
*
*  This license is based on the BSD license adopted by the Apache Foundation.
*
*  $Id: Text.java,v 1.4 2007/01/26 21:54:26 bondolo Exp $
*/

package net.jxta.myjxta.dialog.util;

import net.jxta.myjxta.util.Constants;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Text manipulation utility methods.
 * 
 * @author james todd [gonzo at jxta dot org]
 * @author mike mcangus [mcangus at jxta dot org]
 */
public final class Text {

    /**
     * A regular expression used to find HTML/XML markup for the replace method.
     */
    public static final String MARKUP = "(<[^>]*>)";
    /**
     * A regular expression used to find HTML/XML character entities (e.g. &amp;nbsp;) for the replace method.
     */
    public static final String ENTITY = "(&*[^;]*;)";
    /**
     * A regular expression used to find MyJxta preamble string for the replace method.
     */
    public static final String PREAMBLE = "(\\[[^@]*@[^\\]]*\\])";
    /**
     * An empty string. For use in the replace method.
     */
    public static final String EMPTY = "";

    // Hex Dump Constants
    private static final String NO_HEX_DUMP  = "  No Hex Dump Available.";
    private static final String NULL_STRING  = "  Supplied String is null.";
    private static final String NULL_ARRAY   = "  Supplied Char Array is null.";
    private static final String EMPTY_STRING = "  Supplied String is empty.";
    private static final String EMPTY_ARRAY  = "  Supplied Char Array is empty.";
    private static final String HEX_DUMP_HEADER = Constants.CRLF + "    Hex Dump:";
    private static final String CLOSE_QUOTE = "\" ";
    private static final String OPEN_QUOTE = "  \"";
    private static final String SPACE = " ";

    static final Logger LOG = Logger.getLogger(Text.class.getName());

    private static final Map<String, Pattern> patterns = new HashMap<String, Pattern>();
    
    static {
        addPattern(MARKUP);
        addPattern(ENTITY);
        addPattern(PREAMBLE);
    }

    /**
     * This class is not meant to be instantiated.
     */
    private Text() {
        //This class is not meant to be instantiated.
    }

    /**
     * Deletes blank lines from the supplied multi-line string.
     * 
     * @param string The multi-line String.
     * @return The string with all blank lines removed.
     */
    public static String deleteBlankLines(String string) {
        if (string == null) {
            return null;
        }
        BufferedReader reader = new BufferedReader(new StringReader(string));
        StringBuffer retString = new StringBuffer();
        String line = null;
        boolean firstLine = true;
        try { 
            line = reader.readLine();
        } catch (IOException ioe) {
            // Can't read the String (very unlikely) so we just return an empty string.
            LOG.log(java.util.logging.Level.SEVERE, "Caught unexpected Exception",
                    ioe);
            LOG.log(java.util.logging.Level.SEVERE, "Returning empty String");
            return retString.toString();
        }
        while (line != null) {
            String temp = line.trim();
            if (temp.length() > 0) {
                if (firstLine) {
                    firstLine = false;
                } else {
                    retString.append(Constants.CRLF);
                }
                retString.append(temp);
            }

            try { 
                line = reader.readLine();
            } catch (IOException ioe) {
                // Can't read the String (very unlikely) so we return as much as we were able to process.
                LOG.log(java.util.logging.Level.SEVERE,
                        "Caught unexpected Exception", ioe);
                LOG.log(java.util.logging.Level.SEVERE,
                        "Returning partially completed String");
                return retString.toString();
            }
        }
        return retString.toString();
    }

    /**
     * Scans the supplied orig String for all occurrences of subStr and replaces those occurrences with the repl
     * String.  This method should be used instead of {@link #replace(String, String, String)} below when the
     * replacement is based on a simple substring and not on a pattern.  This helps prevent the Map of patterns
     * that <code>replace()</code> maintains from getting larger than necessary.
     *
     * @param orig The original String.
     * @param subStr The substring that is to be replaced in orig.
     * @param repl The String that is to be substituted for subStr.
     *
     * @return The String after replacement.
     */
    public static String findReplace(String orig, String subStr, String repl) {
        if ((orig == null) || (orig.length() == 0)) {
            return orig;
        }

        StringBuffer buff = new StringBuffer(orig.length());
        int beginIndex = 0;
        int endIndex = orig.indexOf(subStr);
        int subStrLen = subStr.length();

        while (endIndex > -1) {
            buff.append(orig.substring(beginIndex, endIndex));
            buff.append(repl);
            beginIndex = endIndex + subStrLen;
            endIndex = orig.indexOf(subStr, beginIndex);
        }

        if (beginIndex < orig.length()) {
            buff.append(orig.substring(beginIndex));
        }

        return buff.toString();
    }

    /**
     * Replaces each substring of the <code>orig</code> String that matches the given 
     * <a href="../util/regex/Pattern.html#sum">regEx</a> with the given
     * <code>repl</code> String.
     *
     * @param orig The original String that.
     * @param regEx A regular expression to be matched within <code>orig</code>
     * @param repl The string to be substituted for each match.
     * 
     * @return The String that results after all substitutions have been made.
     */
    public static String replace(String orig, String regEx, String repl) {
        Pattern pattern = getPattern(regEx);
        String retVal = orig;
    
        if (pattern != null) {
            Matcher matcher = pattern.matcher(orig);

            if (matcher.find()) {
                retVal = matcher.replaceAll(repl);
            }
        }

        return retVal;
    }

    /**
     * Creates a Hex Dump of contents the supplied String.
     * 
     * <p>
     * Each line represents up to 16 characters of the text String.  The first part of the line is the character
     * representation.  If a character represents an unprintable or non-ascii character, then it is represented
     * by a period ('.').  The second part of the line is the hexedecimal representation of the characters.
     * </p>
     *
     * @param text A String whose contents are to be displayed as a Hex Dump.
     *
     * @return The String containing the Hex Dump.
     */
    public static String hexDump(String text) {
        if (text == null) {
            return NO_HEX_DUMP + NULL_STRING;
        }

        if (text.length() == 0) {
            return NO_HEX_DUMP + EMPTY_STRING;
        }

        return hexDump(text.toCharArray(), 0, text.length());
    }

    /**
     * Creates a Hex Dump of the supplied char array.
     * 
     * <p>
     * Each line represents up to 16 characters of the target array.  The first part of the line is the character
     * representation.  If a character represents an unprintable or non-ascii character, then it is represented
     * by a period ('.').  The second part of the line is the hexedecimal representation of the characters.
     * </p>
     *
     * @param charArray A Char Array whose contents are to be displayed as a Hex Dump.
     *
     * @return The String containing the Hex Dump.
     */
    public static String hexDump(char charArray[]) {
        if (charArray == null) {
            return NO_HEX_DUMP + NULL_ARRAY;
        }

        return hexDump(charArray, 0, charArray.length);
    }

    /**
     * Creates a Hex Dump of the supplied char array.
     * 
     * <p>
     * Each line represents up to 16 characters of the target array.  The first part of the line is the character
     * representation.  If a character represents an unprintable or non-ascii character, then it is represented
     * by a period ('.').  The second part of the line is the hexedecimal representation of the characters.
     * </p>
     *
     * @param charArray A Char Array whose contents are to be displayed as a Hex Dump.
     * @param start The offset into the charArray where the Hex Dump begins.
     * @param length The length (number of chars) of the Hex Dump.<br>
     *        If <code>start + length >= charArray.length</code> then all the characrers from start to the end of
     *        the array are dumped.
     *
     * @return The String containing the Hex Dump.
     */
    public static String hexDump(char charArray[], int start, int length) {
        if (charArray == null) {
            return NO_HEX_DUMP + NULL_ARRAY;
        }

        if (charArray.length == 0) {
            return NO_HEX_DUMP + EMPTY_ARRAY;
        }

        if ((start >= charArray.length) || (start < 0)) {
            return NO_HEX_DUMP + "  Indexing error:  start = " + start + ", length = " + length;
        }

        // indexes into the char array.
        int i = 0;
        int j = 0;
        int k = 0;
        int end = start + length;

        if (end > charArray.length) {
            end = charArray.length;
        }

        int charsPerLine = 16;
        StringBuffer buff = new StringBuffer(length * 5);
        buff.append(HEX_DUMP_HEADER);

        for (i = start; i < end; i += charsPerLine) {
            buff.append(Constants.CRLF);

            String lineNum = Integer.toHexString(i - start).toUpperCase();
            switch (lineNum.length()) {
                case 1: buff.append("  000" + lineNum);
                break;
                case 2: buff.append("  00" + lineNum);
                break;
                case 3: buff.append("  0" + lineNum);
                break;
                default: buff.append("  " + lineNum);
                break;
            }

            // begin quote for char dump
            buff.append(OPEN_QUOTE);

            // create the char dump
            for (j = 0; (j < charsPerLine) && ((i + j) < end); j++) {
                char currChar = charArray[i + j];

                if ((currChar < 0x20) || (currChar > 0xff)) {
                    buff.append('.');
                } else {
                    buff.append(currChar);
                }
            }

            // end quote for char dump
            buff.append(CLOSE_QUOTE);

            // if the line is short, fill in with spaces so hex dump lines up
            for (k = j; k < charsPerLine; k++) {
                buff.append(SPACE);
            }

            // create the hex dump
            for (k = 0; k < j; k++) {
                buff.append(SPACE);
                if (charArray[i + k] < 0x10) {
                    buff.append("000");
                } else if (charArray[i + k] < 0x100) {
                    buff.append("00");
                } else if (charArray[i + k] < 0x1000) {
                    buff.append("0");
                }
                buff.append(Integer.toHexString(charArray[i + k]));
            }
        }

        return buff.toString();
    }

    private static Pattern addPattern(String s) {
        Pattern p = null;
        
        try {
            p = Pattern.compile(s);
        } catch (PatternSyntaxException pse) {
            LOG.log(java.util.logging.Level.SEVERE, "Caught unexpected Exception",
                    pse);
        }
        
        if (p != null) {
            patterns.put(s, p);
        }
        
        return p;
    }

    private static Pattern getPattern(String s) {
        Pattern p = patterns.get(s);
        
        return p != null ? p : addPattern(s);
    }
}
